﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Bitcoin.IO;
using Bitcoin.Test.TestData;
using NUnit.Framework;
using Org.BouncyCastle.Utilities.Encoders;

namespace Bitcoin.Test
{
    [TestFixture]
    public class TransactionTest
    {
        private static readonly ChainParameters _testParams = ChainParameters.TestNet();
        private static readonly ChainParameters _prodParams = ChainParameters.ProdNet();

        private const string _trans1 =
            "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000";

        private const string _trans2 =
            "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff07041844011d0142ffffffff0100f2052a01000000434104a313febd5f91b6a13bd9c5317030518fee96d1319a0eb10076917294933d09c17dc1588a06953a264738f2acea0c66b99e796caa4f28158e0dd5f6fed69a185bac00000000";

        private const string _trans3 =
            "0100000001aa18a952c3f73e5d7440bc570b2aa78f72059887b25b6a1790514b7feedec090000000008b483045022100a970ee6e96fa8bea1cf76d3bda3fb70441a6ec50014d4ea3adcdeae9fbfb5129022025ce9e090366dd6175071a0a5b4a4727571b9bd7bdd5a74d3d3bad7f63eb5dd4014104ac44bdf511477465cb70fef1d06b9241e74d26047ccbdfa641ec9a0115ad35594cbb58a61a6fd56893a405bcffbf6555995ddedc7e6cd4e5ceb83a37e1cf8f98ffffffff02004d92d86a0000001976a914b8083945473bc8289efb681f94de7b07a5b851ad88ac00743ba40b0000001976a914ef01911c9efec6799d1ee5f7c6fb072d9669da8088ac00000000";

        [Test]
        public void TestTran2()
        {
            // TODO: Note that this was reverse engineered from a system that is passing the BlockTest
            // of calculating the Block hash (from the Merkle tree, containing this transaction).
            // It should be independently verified.

            var expected = "03d7811fc7138b38aa3a579f89073853580913f19a1047a258f3dda8c37fa7fb";

            var bytes = Hex.Decode(_trans2);
            var tran2 = new Transaction(_testParams, bytes);
            var actual = tran2.HashAsString;

            Assert.AreEqual(expected, actual);

            var sha256 = new SHA256Managed();
            var hash1 = sha256.ComputeHash(bytes);
            var hash2 = sha256.ComputeHash(hash1);
            CollectionAssert.AreEqual(hash2, tran2.Hash.Bytes);
        }

        [Test]
        public void TestTran2Read()
        {
            var expected = "03d7811fc7138b38aa3a579f89073853580913f19a1047a258f3dda8c37fa7fb";

            var bytes = Hex.Decode(_trans2);

            Transaction tran2;
            using (var s = new MemoryStream(bytes))
            using (var r = new BinaryReader(s))
            {
                tran2 = r.ReadTransaction();
            }

            var actual = tran2.HashAsString;

            Assert.AreEqual(expected, actual);
        }


        [Test]
        public void TestTran2Reserialize()
        {
            var bytes = Hex.Decode(_trans2);
            var tran2 = new Transaction(_testParams, bytes);

            var bytes2 = tran2.BitcoinSerialize();

            CollectionAssert.AreEqual(bytes, bytes2);
        }

        [Test]
        public void TestTran2Write()
        {
            // TODO: Note that this was reverse engineered from a system that is passing the BlockTest
            // of calculating the Block hash (from the Merkle tree, containing this transaction).
            // It should be independently verified.

            var expected = "03d7811fc7138b38aa3a579f89073853580913f19a1047a258f3dda8c37fa7fb";

            var bytes = Hex.Decode(_trans2);
            var tran2 = new Transaction(_testParams, bytes);

            byte[] bytes2;
            using (var s = new MemoryStream())
            using (var w = new BinaryWriter(s))
            {
                w.Write(tran2);
                bytes2 = s.ToArray();
            }

            CollectionAssert.AreEqual(bytes, bytes2);
        }


        [Test]
        [TestCaseSource(typeof(TxValidData))]
        public void TestTransactionData(List<Tuple<string, int, string>> inputs, string serializedTransaction,
            bool enforceP2SH)
        {
            Console.WriteLine("Testing {0} inputs, '{1}'", inputs.Count, inputs.Count > 0 ? inputs[0].Item1 : "");
            var state = new ValidationState();

            var bytes = Hex.Decode(serializedTransaction);
            var tx = new Transaction(_testParams, bytes);
            
            Assert.IsTrue(tx.Verify(state));
        }
    }
}
